#include <mm.h>
#include <mm_copy.h>
#include <vga.h>
#include <string.h>
#include <i386.h>
#include <debug.h>
#include <loader.h>

extern task task_table[MAX_PID + 1];

#define DEBUG_MM_PF(x)
//#define DEBUG_MM_PF(x) x

void
mm_copy_vt2phy( void * phy_dest, void * vt_src )
{
	mm_page * table_entry;
	
	if ( vt_src == (void*)PAGE_COPY_BUFFER)
	{
		vga_write(0,0,"mm_copy_vt2phy ASSERT!!",VGA_FC_WHITE);
		while(1);
	}
	
	//creo una entrada temporal para un buffer de copia
	table_entry = mm_get_vt_addr_entry((void *) rcr3(),(void *) PAGE_COPY_BUFFER);
	table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_S;
	table_entry->base = ((uint_32) phy_dest) >> 12;

	//copio la pagina
	memcpy((void *) PAGE_COPY_BUFFER, vt_src, PAGE_SIZE);

	//elimino la entrada temporal
	*((uint_32*) table_entry) = 0;
	invlpg((void*)PAGE_COPY_BUFFER);
}

void
mm_copy_phy2vt( void * vt_dest, void * phy_src )
{
	mm_page * table_entry;
	
	if ( vt_dest == (void*)PAGE_COPY_BUFFER)
	{
		vga_write(0,0,"mm_copy_phy2vt ASSERT!!",VGA_FC_WHITE);
		while(1);
	}
	
	//creo una entrada temporal para un buffer de copia
	table_entry = mm_get_vt_addr_entry((void *) rcr3(),(void *) PAGE_COPY_BUFFER);
	table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US_S;
	table_entry->base = ((uint_32) phy_src) >> 12;

	//copio la pagina
	memcpy(vt_dest, (void *) PAGE_COPY_BUFFER, PAGE_SIZE);

	//elimino la entrada temporal
	*((uint_32*) table_entry) = 0;
	invlpg((void*)PAGE_COPY_BUFFER);
}

void *
mm_copy_kpage(void * source)
{
	void * dest = NULL;
	dest = mm_mem_kalloc(); 
	memcpy(dest, source, PAGE_SIZE);
	return dest;
}

/* 
 * mode == 0 full
 * mode == 1 cow
 */
void
mm_copy_user_pages( void * base, mm_page* tabla, int mode)
{
	int j = 0;
	void * page = NULL;
	
	for (j = 0; j < 1024; j++)
	{
		if(mm_entry_is_null(&(tabla[j])))
			continue;

		// Es compartida
		if (tabla[j].attr & MM_ATTR_SHR)
			continue;
			
		// Es copy on write
		if ( mode && tabla[j].attr & MM_ATTR_COW)
			continue;
		
		// Es Read Only	
		if (!(tabla[j].attr & MM_ATTR_RW))
			continue;
			
		void * vt_addr = (void*)(base + (j * PAGE_SIZE));
		
		//debug_log(" Copiando %x \n", vt_addr);
		
		page = mm_mem_alloc();
		mm_copy_vt2phy( page, vt_addr );
		tabla[j].base = ((uint_32) page) >> 12;
	}
}

mm_page *
mm_copy_directory_page()
{
	mm_page *h_dir, *p_dir;
	//Copio el directorio de paginas
	p_dir = (mm_page *) rcr3(); 
	h_dir = mm_copy_kpage(p_dir);
	memcpy(h_dir, p_dir, PAGE_SIZE);
	return h_dir;
}

void
mm_copy_set_table_cow(mm_page * tabla, char* vt_base)
{
	int j;
	for( j = 0; j < 1024; j++)
	{
		if(mm_entry_is_null(&(tabla[j])))
			continue;
		
		// Esta página si se copia
		if ( (vt_base + j * PAGE_SIZE) == (char*)PILA_0 )
			continue;
		if ( (vt_base + j * PAGE_SIZE) == (char*)PILA_3 )
			continue;
			
		// Permite Write ??
		if (!(tabla[j].attr & MM_ATTR_RW))
			continue;
		
		// Es compartida
		if (tabla[j].attr & MM_ATTR_SHR)
			continue;
	
		// Invalido TLB
		invlpg(vt_base + j * PAGE_SIZE);
		// Clean Write
		tabla[j].attr = tabla[j].attr & (~MM_ATTR_RW);
		// Marco copy on write
		tabla[j].attr |= MM_ATTR_COW;
	}
}

mm_page *
mm_copy_current_directory_full()
{
	
	mm_page * h_dir = mm_copy_directory_page();

	int i;
	for (i = 1; i < 1024; i++)
	{
		if (mm_entry_is_null(&(h_dir[i]))) {
			continue;
		}
		
		mm_page *h_table = NULL;
		h_table = (mm_page *) (h_dir[i].base << 12);
		h_table = mm_copy_kpage(h_table);
		h_dir[i].base = ((uint_32) h_table) >> 12; 
		
		mm_copy_user_pages( (void *) (i * 1024 * PAGE_SIZE), h_table, 0);
		
	}
	//breakpoint();
	return h_dir;
}

mm_page *
mm_copy_current_directory_cow()
{
	
	mm_page * h_dir = mm_copy_directory_page();

	int i;
	for (i = 1; i < 1024; i++)
	{
		if (mm_entry_is_null(&(h_dir[i]))) {
			continue;
		}
		
		mm_page *h_table = NULL;
		h_table = (mm_page *) (h_dir[i].base << 12);
		mm_copy_set_table_cow(h_table, (void *) (i * 1024 * PAGE_SIZE));
		h_table = mm_copy_kpage(h_table);
		h_dir[i].base = ((uint_32) h_table) >> 12; 

		// Con modo == 1 solo copia las que no tienen la marca de cow
		mm_copy_user_pages( (void *) (i * 1024 * PAGE_SIZE), h_table, 1);
	}
	//breakpoint();
	return h_dir;
}

int
mm_copy_cant_mapped_to(void * vt_addr, uint_32 phy_page)
{
	int cont = 0;
	int pid;
	phy_page = phy_page >> 12;
	for (pid = 0; pid < MAX_PID; pid++)
	{
		if (task_table[pid].status == TASK_FREE)
			continue;
		if (task_table[pid].status == TASK_FINISHED)
			continue;	

		mm_page * table_entry = NULL;
		table_entry = mm_get_vt_addr_entry((mm_page *)task_table[pid].cr3, vt_addr);
		if ( ! table_entry->attr & MM_ATTR_P)
			continue;
			
		if ( table_entry->base == phy_page )
			cont ++;
	}
	return cont;
}

void
mm_copy_cow_map(mm_page * table_entry, char * vt_addr)
{
	DEBUG_MM_PF(debug_log("- Attr Old %b \n", table_entry->attr);)
	DEBUG_MM_PF(debug_log("- Base Old %x \n", table_entry->base);)
	// Pagina fisica a copiar
	void * phy_page = (void*)(table_entry->base << 12);
	
	int cant_ref = mm_copy_cant_mapped_to(vt_addr, (uint_32) phy_page);
	DEBUG_MM_PF(debug_log("- # Ref %d \n", cant_ref);)
	
	if (cant_ref > 1)
	{
	
		// Mapeo página nueva
		void * new_page = mm_mem_alloc();
		table_entry->base = ((uint_32) new_page) >> 12;
	
		// Arreglo atributos
		table_entry->attr &= ~MM_ATTR_COW;
		table_entry->attr |= MM_ATTR_RW_W;
		
		// Invalido TLB
		invlpg(vt_addr);
		DEBUG_MM_PF(debug_log("- Attr New %b \n", table_entry->attr);)
		DEBUG_MM_PF(debug_log("- Base New %x \n", table_entry->base);)
		
		// Copio datos
		mm_copy_phy2vt( vt_addr, phy_page );
	}
	else
	{
		DEBUG_MM_PF(debug_log("- Ultima copia - \n");)
		
		// Arreglo atributos
		table_entry->attr &= ~MM_ATTR_COW;
		table_entry->attr |= MM_ATTR_RW_W;
		
		// Invalido TLB
		invlpg(vt_addr);
	}
}

void
mm_copy_map_new(mm_page * table_entry)
{
	void *ps_page;
	ps_page = mm_mem_alloc();
	
	//si no hay paginas libres VER - Matar Proceso
	//if (NULL == ps_page) {
	//	return NULL;
	
	table_entry->attr = MM_ATTR_P | MM_ATTR_RW | MM_ATTR_US;
	table_entry->base = ((uint_32) ps_page) >> 12;
	
	DEBUG_MM_PF(debug_log("- Attr New %b \n", table_entry->attr);)
	DEBUG_MM_PF(debug_log("- Base New %x \n", table_entry->base);)
}

void
mm_copy_page_fault(const uint_32* stack, const exp_state* expst)
{
	char * pf;
	mm_page * table_entry;
	//breakpoint();
	
	pf = (char*)(rcr2() & 0xFFFFF000); 
	
	DEBUG_MM_PF(debug_log(" PF %x ADDR %x \n", pf, rcr2());)
	DEBUG_MM_PF(debug_log(" ErrorCode %b\n", expst->errcode);)
	
	table_entry = mm_get_vt_addr_entry((void *) rcr3(), pf);
	
	if ( table_entry->attr & MM_ATTR_COW )
	{
		DEBUG_MM_PF(debug_log("- Se Mapea \n", pf);)

		mm_copy_cow_map(table_entry, pf);
		
		DEBUG_MM_PF(debug_log("- Done \n", pf);)
		//~ breakpoint();
		return;
	}
	
	if ( table_entry->attr & MM_ATTR_TBA )
	{
		DEBUG_MM_PF(debug_log("- Se Mapea Nueva pagina de usuario \n", pf);)
		mm_copy_map_new(table_entry);
		invlpg(pf);
		DEBUG_MM_PF(debug_log("- Done \n", pf);)
		//~ breakpoint();
		return;
	}
	
	debug_log("- Panic \n", pf);
	breakpoint();
	debug_kernelpanic(stack, expst);
}

void
mm_copy_page_entry_free(mm_page * table_entry, void * vt_addr)
{
	//si la entrada en la tabla de pagina tiene una pagina asociada la libero
	if ( mm_entry_is_null(table_entry) )
		return;
		
	//if (*((uint_32 *) table_entry)) 
	
	// La página no fue utilizada -> NO está asignada
	if ( table_entry->attr & MM_ATTR_TBA )
		return;
	
	uint_32 phy = (table_entry->base << 12);
	
	// Tiene marca de COPY ON WRITE
	if ( table_entry->attr & MM_ATTR_COW )
	{
		// Si es la ultima la libero
		if (mm_copy_cant_mapped_to(vt_addr, phy) <= 1)
		{
			mm_mem_free((void *) phy);
		}
		return;
	}
	
	// Tiene marca de Shared
	if ( table_entry->attr & MM_ATTR_SHR )
	{
		// Si es la ultima la libero
		if (mm_copy_cant_mapped_to(vt_addr, phy) <= 1)
		{
			mm_mem_free((void *) phy);
		}
		return;
	}
	
	// Default
	mm_mem_free((void *) phy);
	
}

